#######################################################################
#
#	CAL STATE CHAMELEON PLUGIN
#	version 3.4
#
#	(c) 2009 California State University
#	By David Walker
#	dwalker@calstate.edu
#	
#	This library is free software; you can redistribute it and/or
#	modify it under the terms of the GNU Lesser General Public
#	License as published by the Free Software Foundation; either
#	version 3 of the License, or (at your option) any later version.
#
#	This library is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#	Lesser General Public License for more details.
#
#	http://www.gnu.org/licenses/lgpl.txt
#
########################################################################

package Parsers::PlugIn::Chameleon;

use strict;
use CGI::Carp;
use Data::Dumper;
use HTML::Entities;
use XML::Simple;
use ZOOM;

# DEBUG
#
# returns the debugging messages showing the module's search of the catalog
# records returned, and processing of holdings
#
# @return string debugging message

sub debug
{
	my ( $self ) = @_;
	my $version = "chameleon version 3.6 (April 29, 2014)";
	return $version . "\n\n\n" . $self->{'debug'};
}


# LOOKUP
#
# this is the function that all SFX plug-ins must implement
#
# @param object $self     perl's wacky object oriented programming hack
# @param object $ctx_obj  the SFX context object passed by the main sfx program
# @return bool            true if journal holdings indicate a match or book is available, otherwise false

sub lookup
{	
	my ( $self, $ctx_obj ) = @_;
	
	# openurl request data
	
	my $issn = $ctx_obj->get('rft.issn');
	my $isbn = $ctx_obj->get('rft.isbn');
	my $year = $ctx_obj->get('rft.year');
	
	# strip isbn dashes
	
	$isbn =~ s/-//g;
	
	my $bolFinal = 0;
	my $query = "";
	my $recordSyntax = "OPAC";
	my $elementSetName = "b";
	
	if ( $self->{'record_syntax'} ne undef )
	{
		$recordSyntax = $self->{'record_syntax'};
	}
	
	# set the query based on the request
	
	if ( $issn ne "" )
	{
		$query = '@attr 1=8 ' . $issn;
	}
	elsif ( $isbn ne "" )
	{
		$query = '@attr 1=7 ' . $isbn;
		
		if ( $self->{'vendor'} eq "Innovative" )
		{
			$elementSetName = 'f';
		}
	}
	
	$self->{'debug'} .= "\n\n=======================";
	$self->{'debug'} .= "\nSearch Information:";
	$self->{'debug'} .= "\n=======================\n\n";
	$self->{'debug'} .= "address: " . $self->{'address'} . ":" . $self->{'port'} . "\n";
	$self->{'debug'} .= "database: " . $self->{'database'} . "\n";
	$self->{'debug'} .= "query: " . $query . "\n";
	$self->{'debug'} .= "\n\n";
	
	# establish a connection with a z39.50 target
		
	my $conn = new ZOOM::Connection($self->{'address'}, $self->{'port'}, 
		databaseName => $self->{'database'},
		preferredRecordSyntax => $recordSyntax,
		timeout => $self->{'timeout'}
	);
		
	if (! $conn)
	{
		# could not connect to target, show the catalog just in case
		
		$self->{'debug'} .= "\n\nError: Could not connect to target: \n";
		
		$bolFinal = 0;
		
		die($self->{'debug'});
	} 
	else 
	{
		# search for the item
		
		my $rs = $conn->search_pqf($query);
				
		$rs->option(preferredRecordSyntax => $recordSyntax);
		$rs->option(elementSetName => $elementSetName);
				
		if ( $rs->size() == 0 )
		{
			$self->{'debug'} .= "\n\nDid not find match\n\n";
		} 
		else 
		{
			# parse the records as needed based on the supplied action
			
			if ( $issn ne "" )
			{
				$bolFinal = $self->checkJournal( $rs, $issn, $year );
			}
			elsif ( $isbn ne "" )
			{
				$bolFinal = $self->checkBook( $rs, $isbn );
			}
		}
		
		$conn->destroy();
	}
	
	$self->{'debug'} .= "\n\n======================================";
	$self->{'debug'} .= "\nResult";
	$self->{'debug'} .= "\n";
	$self->{'debug'} .= "\nA one means show the link to the catalog; ";
	$self->{'debug'} .= "\na zero means don't show the link.";
	$self->{'debug'} .= "\n=========================================\n\n";
	
	$self->{'debug'} .= "\nAs a module would have returned: <h2>$bolFinal</h2>";
	
	
	# log debugging messages
	
	if ( $self->{'log'} ne undef )
	{
		warn( $self->debug() );
	}
	
	return $bolFinal;
}

sub checkJournal
{
	my ( $self, $rs, $issn, $year ) = @_;
	
	my $holdings = '';
	my $found = 0;
	
	# current year and month
	
	my $this_year = (localtime)[5] + 1900;
	my $this_month = (localtime)[4] + 1;
	
	# convert comma seperated configuration values here to array
	
	my @rolling_holdings = split(/,/, $self->{'rolling_holdings'} );
	my @bib_fields = split(/,/, $self->{'bib_fields'} );
	
	# run thru all returned records
	
	for ( my $i = 0; $i < $rs->size(); $i++ )
	{	
		# we'll use this as a check on skipped holdings
		
		my $bol_skipped = 0;
		
		# retrieve and render record
		
		my $rec = $rs->record($i);
		my $marc = $rec->render();
		
		# clean-up
		
		$marc =~ s/\s{7}//gm;
		
		# check to make sure record has an issn to avoid matches on isbn in
		# systems like Innovative where both typically share the same index
		
		if ( $issn ne "" && $marc =~ /^022.*$/gm )
		{
			$self->{'debug'} .= "\n\n==================================================";
			$self->{'debug'} .= "\nRecord $i: Original Record";
			$self->{'debug'} .= "\n";
			$self->{'debug'} .= "\nThis is the original record returned by the catalog.";
			$self->{'debug'} .= "\n====================================================\n\n";
			
			$self->{'debug'} .= encode_entities("$marc");
			
			$self->{'debug'} .= "\n\n========================================================";
			$self->{'debug'} .= "\nRecord $i: Journal Processing";
			$self->{'debug'} .= "\n";
			$self->{'debug'} .= "\nHere we will attempt to boil down the holdings into year ";
			$self->{'debug'} .= "\nranges, and compare the year of the article against the ";
			$self->{'debug'} .= "\nranges to see if the request falls within our holdings. ";
			
			if ( $i > 1 )
			{
				$self->{'debug'} .= "\n( Note that the holdings below accumulate from earlier records ). ";
			}
			
			$self->{'debug'} .= "\n===========================================================\n\n";
			
			my @holdings_array = split(/<holding>/, $marc);
			
			# holdings from attached holdings records
			
			foreach (@holdings_array)
			{
				my $holdingsData = $_;
				
				if ( $self->{'vendor'} eq "Innovative" )
				{
					my $ignore_location = $self->{'ignore_locations'};
				
					my $location = "";
					my $publicNote = "";
					
					if ( $holdingsData =~ /<localLocation>([^<]*)/gm )
					{
						$location = $1;
					}

					if ( $holdingsData =~ /<publicNote>([^<]*)/gm )
					{
						$publicNote = $1;
					}
					
					# only add locations that are not in our exlcude list
					
					if ( $ignore_location =~ /$location/ )
					{
						$self->{'debug'} .= "Skipped holdings from location '$location' \n";
						$bol_skipped = 1;
					}
					else
					{
						if ( $holdings eq "" )
						{
							$holdings = $publicNote;
						}
						else
						{
							$holdings .= $self->{'delimiter'} . " " . $publicNote;
						}
					}
				}

				elsif ( $self->{'vendor'} eq "Endeavor" )
				{
					if ( $holdingsData =~ /<enumAndChron>([^<]*)/gm )
					{
						if ( $holdings eq "" )
						{
							$holdings = $1;
						}
						else
						{
							$holdings .= $self->{'delimiter'} . " " . $1;
						}
					}
				}
			}
			
			# holdings from marc record
			# only do this if the config includes bib fields
			
			my $bibcount = @bib_fields;
			
			if ( $bibcount >= 1 )
			{			
				# we'll cycle thru the record parsing out each
				# datafield in the bib record
				
				while ( $marc =~ /^([0-9]{3})(.*)$/gm )
				{
					my $tag = $1;
					my $data = $2;
					
					# we'll cycle thru the values in the config array to
					# see if the current datafield matches the tag in the 
					# config
					
					for my $bibfield ( @bib_fields )
					{
						if ( $bibfield =~ /([0-9]{3})_([a-z]{1})/ )
						{
							my $bibtag = $1;
							my $bibcode = $2;
							
							# on a match, we'll extract the subfields
							# and compare those against the subfield code
							# in the config; add its value to the holdings if it matches
							
							if ( $tag eq $bibtag )
							{						
								while ( $data =~ /\$([a-z0-9])([^\$]*)/g )
								{
									if ( $bibcode eq $1 )
									{
										if ( $holdings eq "" )
										{
											$holdings = $2;
										}
										else
										{
											$holdings .= $self->{'delimiter'} . " " . $2;
										}
									}
								}
							}
						}		
					}
				}
			}
			
			# if no holdings found
			
			if (!$holdings)
			{
				# this is kind of rough logic, but if there are no holdings and locations 
				# were skipped, then it's likely that this should not be displayed
				
				if ( $bol_skipped == 1 )
				{
					$self->{'debug'} .= "No holdings, with skipped location, so pressumed not to show \n";
				}
				
				# no holdings, but has 856 field, likely an electronic journal					
				
				elsif ( $marc =~ /^856/m )
				{
					if ( $self->{'show-with-856'} == 1 )
					{
						# choose to show 856 anyway
						$self->{'debug'} .= "You are choosing to show this record even though it has no holdings and an 856 \n";
						$found = 1;
					}
					else
					{
						# ignore since SFX KB will have caught this if there is full-text
						$self->{'debug'} .= "This is presumed to be an e-journal since it has an 856 field \n";
					}
				}
				else 
				{
					$found = 1;
				}
			}
			
			# if there was no year in the incoming request
			
			elsif (! $year && ! $self->{'enforce-year'})
			{
				$self->{'debug'} .= "There is no year in the request, so will show journal solely on ISSN match \n";
				$found = 1;
			}

			# show link if _any_ holdings were found without processing the statements
			
			elsif ( $self->{'print-only'} )
			{
				$self->{'debug'} .= "Found holdings, will show since this is 'print-only'. \n";
				$found = 1;
			}
			else
			{
				# convert rolling holdings to year range
				
				foreach my $rolling ( @rolling_holdings )
				{
					my $bolYear = 0;	# the statement concerns a single year
					my $bolYears = 0;	# the statement concerns multiple years
					my $bolMonth = 0;	# the statement concerns months
					
					$rolling =~ s/#/([0-9]{1,3})/;
					$rolling = $self->trim($rolling);
					
					if ( $rolling =~ /month/ )
					{
						$bolMonth = 1;
					}
					elsif ( $rolling =~ /years/ )
					{
						$bolYears = 1;
					}
					elsif ( $rolling =~ /year/ )
					{
						$bolYear = 1;
					}
					
					if ( $holdings =~ /$rolling/i )
					{
						my $temp = "";
						
						if ( $bolYears == 1 )
						{
							$temp = $this_year - $1 + 1;
						}
						elsif ( $bolYear == 1 )
						{
							$temp = $this_year;
						}
						elsif ( $bolMonth == 1 )
						{
							# need to take the month statement back
							# to whole year; so go back one year if
							# month statement pushes us back before
							# January
							
							if ( $this_month - $1 > 0 )
							{
								$temp = $this_year;
							}
							else
							{
								$temp = $this_year - 1;
							}
						}
						
						$holdings =~ s/$rolling/$temp-9999/i;
					}
				}
					
				# simplify serial solutions dates to avoid the commas contained 
				# therein from tripping up our run delimiter
				
				if ( defined $self->{'full_date_to_year'} && $self->{'full_date_to_year'} == 1 )
				{
					$holdings =~ s/\([^)]{1,}([0-9]{4})\)/($1)/g;
				}
				
				# strip out any remaining alpha characters
				
				$holdings =~ s/[a-zA-Z]//g;
				
				# normalize multiple delimiters
				
				if ( $self->{'delimiter_replace'} ne "" && $self->{'delimiter'} ne "" )
				{
					my $find = $self->{'delimiter_replace'};
					my $replace = $self->{'delimiter'};
										
					$holdings =~ s/$find/$replace/g;
				}
				
				# split the statement based on the delimiter
				
				my @arrHoldings = split($self->{'delimiter'}, $holdings);
				
				my $span = $self->{'span'};
				
				foreach my $data (@arrHoldings)
				{					
					#convert mixed years to single years
					
					$data =~ s/([0-9]{4})\/([0-9]{2,4})/$1/;
					
					# catch open-ended holdings, but make sure this
					# isn't a complete holding first
					
					if ( $data =~ /.*?[0-9]{4}.*?$span.*?[0-9]{4}.*/ )
					{

					}
					elsif ( $data =~ /.*?([0-9]{4}).*?$span.*/ )
					{
						$data =~ s/(.*?[0-9]{4}.*?)$span.*/$1 $span 9999/;
					}
					
					# split the resulting data on the dash
					
					my @arrRanges;
					my @tempRanges = split($span, $data);
										
					
					# extract just the year; if there is more than one four digit number, 
					# take the last one since this will most likely be the year
				
					foreach my $temp_data (@tempRanges)
					{							
						my $year_last = "";
						
						while ( $temp_data =~ /([0-9]{4})/g )
						{
							$year_last = $1;
						}
						
						if ( $year_last ne "" )
						{
							push(@arrRanges, $year_last);
						}
					}
					
					$self->{'debug'} .= "minimized:\t" . join('-',@arrRanges) . "\t";
					
					# get the length of the array
					
					my $iLength = @arrRanges;
					
					my $bolFound;
					
					# see if the publication date falls within the range
					
					if ( $iLength == 2 )
					{						
						$arrRanges[0] = $self->trim($arrRanges[0]);
						$arrRanges[1] = $self->trim($arrRanges[1]);
					
						if ( $year >= $arrRanges[0] && $year <= $arrRanges[1] )
						{
							$bolFound = 1;
						} 
						else
						{
							$bolFound = 0;
						}
					}
					else
					{
						$arrRanges[0] = $self->trim($arrRanges[0]);
						
						if ( $year == $arrRanges[0] )
						{
							$bolFound = 1;
						}
						else
						{
							$bolFound = 0;
						}
					}
					
					$self->{'debug'} .= "matched:" . $bolFound . "\n";
					
					# if any of the above matched, set the final
					# variable to true
					
					if ( $bolFound == 1 )
					{
						$found = 1;
					}
				}
			}
		}
	}
	
	return $found;
}

sub checkBook
{
	my ( $self, $rs, $isbn ) = @_;
	
	my @availability = split(/,/, $self->{'availability'} );
	
	my $found = 0;
	my $holdings = "";
	
	# run thru all returned records
	for ( my $i = 0; $i < $rs->size(); $i++ )
	{	
		# retrieve and render record
		
		my $rec = $rs->record($i);
		my $marc = $rec->render();
		
		# check to make sure record has an isbn
		# to avoid matches on issn
		
		if ( $isbn ne "" && $marc =~ /^020.*$/gm )
		{
		
			$self->{'debug'} .= "\n\n===================================================";
			$self->{'debug'} .= "\nRecord $i: Original Record";
			$self->{'debug'} .= "\n";
			$self->{'debug'} .= "\nThis is the original record returned by the catalog.";
			$self->{'debug'} .= "\n=====================================================\n\n";
			
			$self->{'debug'} .= encode_entities("$marc");
			
			$self->{'debug'} .= "\n\n======================================================";
			$self->{'debug'} .= "\nRecord$i: Book Processing";
			$self->{'debug'} .= "\n";
			$self->{'debug'} .= "\nHere we will check the availability status of this item ";
			$self->{'debug'} .= "\nto see if a circulating copy exists. ";
			$self->{'debug'} .= "\n======================================================\n\n";
			
			# parse out summary holdings
						
			if ( $self->{'vendor'} eq "Innovative" )
			{
				while ( $marc =~ /<publicNote>([^<]*)/gm )
				{
					if ( $holdings eq "" )
					{
						$holdings = $1;
					}
					else
					{
						$holdings .= ", " . $1;
					}
				}
			}
			elsif ( $self->{'vendor'} eq "Endeavor" )
			{
				while ($marc =~ /<availableNow value="(\d)"\/>/gm )
				{
					if ( $holdings eq "" )
					{
						$holdings = $1;
					}
					else
					{
						$holdings .= ", " . $1;
					}
				}
			}
			
			if (!$holdings)
			{
				# just show it anyway?
				
				if ( exists $self->{'book-print-only'} )
				{
					$found = 0;
				}
				else
				{
					$found = 1;
				}
			} 
			else
			{				
				# split the statement based on the comma
				my @arrHoldings = split(",", $holdings);
				
				foreach my $data (@arrHoldings)
				{
					$data = $self->trim($data);
					my $bolFound = 0;
					
					if ( $self->{'vendor'} eq "Innovative" )
					{		
						foreach my $status ( @availability )
						{
							$status = $self->trim($status);
							
							if ( $data eq $status)
							{
								$bolFound = 1;
							}
						}
					}
					elsif ( $self->{'vendor'} eq "Endeavor" )
					{
						if ( $data eq "1" )
						{
							$bolFound = 1;
						}
					}
					
					$self->{'debug'} .= "\n:$data:\t$bolFound";
					
					if ( $bolFound == 1 )
					{
						$found = 1;
					}
				}
			}
		}
	}
	
	return $found;
}
		

sub trim
{
	my ( $self, $string ) = @_;
	$string =~ s/^\s+//;
	$string =~ s/\s+$//;
	return $string;
}

1;